理解題目:
這題要在字串 s 裡找到一個最小的子字串,讓這個子字串包含字串 t 裡所有的字符(包括重複的字符)。如果找不到,就返回空字串 ""。
思路:
可以用「滑動窗口」解決這類最小子串問題,滑動窗口技術的重點是擴展右邊界找到一個可行的窗口,然後收縮左邊界縮小窗口大小直到找到最小的可行解,保持一個字符頻率計數器,要維護一個字符頻率計數器來跟蹤窗口中是否已經包含了 t 裡所有的字符,這可以用兩個計數器,一個記錄 t 需要配對的字符和它們的數量,另一個記錄當前窗口裡包含的字符和數量。
步驟:
開始從字符串的左端和右端初始化一個窗口,然後右指針漸漸擴展窗口,直到找到一個包含所有 t 字符的子串,找到一個滿足條件的窗口之後,試著縮小左邊界,找到更小的可行窗口。
程式碼:
from collections import Counter
class Solution(object):
def minWindow(self, s, t):
"""
:type s: str
:type t: str
:rtype: str
"""
#邊界條件:如果 t 的長度大於 s 的長度,無法找到符合條件的子串
if len(t) > len(s):
return ""
#算 t 中每個字符出現的次數
t_count = Counter(t)
current_count = {}
#初始化滑動窗口指針
left = 0
min_len = float('inf') # 把最小長度初始化無限大
min_window = ""
#要匹配的字符數
required = len(t_count)
formed = 0 # 記錄當前匹配的字符數
#通過右指針擴展窗口
for right in range(len(s)):
char = s[right]
current_count[char] = current_count.get(char, 0) + 1
#如果當前字符的頻率符合 t 裡的要求
if char in t_count and current_count[char] == t_count[char]:
formed += 1
#當窗口裡的字符數符合 t 的要求,試著縮小窗口
while formed == required:
window_len = right - left + 1
if window_len < min_len:
min_len = window_len
min_window = s[left:right+1]
#移動左指針縮小窗口
left_char = s[left]
current_count[left_char] -= 1
#如果當前字符在 t 裡而且數量小於要求,就減少已配好的字符數
if left_char in t_count and current_count[left_char] < t_count[left_char]:
formed -= 1
left += 1 # 縮小窗口
#如果找到有效的最小窗口,返回結果;不然就返回空字串
return min_window if min_len != float('inf') else ""
解釋:
計數器:t_count:算字符串 t 裡每個字符的出現次數。
current_count:算當前窗口中每個字符的出現次數。
滑動窗口:left 和 right 就是滑動窗口的左右邊界,用來動態調整窗口大小。
滿足 t 裡所有字符要求,接下來試著把窗口縮小。
檢查:窗口裡所有需要的字符都被配到時(即 formed == required),開始縮小窗口。
最小子串更新:找到一個比之前更小的符合條件的窗口時,更新最小窗口長度和內容。
結果返回:返回最小的符合條件的子串,如果沒找到就返回空字串 。
時間複雜度:
O(m + n) , m 是字串 s 的長度,n 是字串 t 的長度。
關鍵:
用滑動窗口確保線性時間的計算,維護兩個計數器,一個跟蹤目標字符頻率,另一個跟蹤窗口內字符匹配情況。
心得:
時間其實過得很快,不知不覺就已經寫了一半了,雖然每天寫偶爾還是會覺得有點煩躁,但是每天動一下腦,習慣了就會好很多,可我真的覺得需要囤一點文,週末會比較可以安排時間,總之今天又學了新東西,對於滑動窗口的運用有比較熟悉一點,這題我也花了不少時間,所以至少比較有印象。